package yc.tool.dbmigrate.conf;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.util.StringUtils;

import com.google.common.base.Strings;
import yc.tool.dbmigrate.common.Constant;
import yc.tool.dbmigrate.entity.DbInfo;
import yc.tool.dbmigrate.entity.FieldInfo;
import yc.tool.dbmigrate.entity.Other;
import yc.tool.dbmigrate.entity.SyncBean;
import yc.tool.dbmigrate.entity.SyncConfig;
import yc.tool.dbmigrate.entity.SyncStatus;
import yc.tool.dbmigrate.entity.TableInfo;
import yc.tool.dbmigrate.entity.Where;
import yc.tool.dbmigrate.exception.SyncConfigException;

/**
 * 配置管理
 * @author yuancan
 *
 */
public class SyncPropConfigurer extends PropertyPlaceholderConfigurer {
//	private static final Logger log = Logger.getLogger(SyncPropConfigurer.class);
	private static final Logger log = LoggerFactory.getLogger(SyncPropConfigurer.class);
	
	private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	private static SyncConfig syncConfig = new SyncConfig();
	public static String SourceDbType = null;
	public static String TagDbType = null;
	
	
	@Override
	protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
			throws BeansException {
		//解析xml
		Element root = null;
		try {
			String path = System.getProperty("user.dir") + "/config/sync.xml";
			log.info("读取配置路径:"+path);
			
			root = getRoot(new FileInputStream(path));
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (DocumentException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		if (null == root) {
			throw new SyncConfigException("配置文件sync.xml读取不到");
		}
		
		//dbInfo
		DbInfo sourceDb = null;
		DbInfo tagDb = null;
		
		Element dbinfo = root.element("db-info");
		for (Iterator i = dbinfo.elementIterator(); i.hasNext(); ) {
			Element element = (Element) i.next();
			if ("source-db".equals(element.getName())) {
				sourceDb = getDbInfo(element);
			} else if ("tag-db".equals(element.getName())) {
				tagDb = getDbInfo(element);
			}
		}
		
		setProperties(props, sourceDb, tagDb);
		
		super.processProperties(beanFactoryToProcess, props);
		
		//other(这个要先于syncBean节点读，因为里面有个关于是否分页查询的配置，涉及到“父子节点配置”的问题)
		Element otherConfig = root.element("other");
		syncConfig.setOther(getOther(otherConfig));
		
		//syncBean
		List<SyncBean> syncBeanList = new ArrayList<>();
		Element syncBeans = root.element("sync-beans");
		Element itemElement = null;
		int index = 1;
		for (Iterator i = syncBeans.elementIterator(); i.hasNext(); ) {
			itemElement = (Element) i.next();
			if ("true".equals(itemElement.attributeValue("ignore"))) {
				continue;//该节点被忽略
			}
			syncBeanList.add(getSyncBean(itemElement, index, syncConfig.getOther().isPageSync()));
			index++;
		}
		
		//根据同步序号排序
		Collections.sort(syncBeanList, new Comparator<SyncBean>() {

			@Override
			public int compare(SyncBean o1, SyncBean o2) {
				return (o1.getSyncIndex() - o2.getSyncIndex());
			}
			
		});
		
		syncConfig.setSourceDb(sourceDb);
		syncConfig.setTagDb(tagDb);
		syncConfig.setSyncBeans(syncBeanList);
		
		SourceDbType = syncConfig.getSourceDb().getDbType();
		TagDbType = syncConfig.getTagDb().getDbType();
		
		//日志输出配置，以确定是否配置出错
		SyncConfigInfoLog();
		log.info("配置加载完成！");
	}
	
	
	public static void SyncConfigInfoLog() {
		log.info("+-+-+-+-+-+-+-+-+-+-+-+-同步信息-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-start-+-+-+-+-+-");
		log.info("+ 源数据(" + syncConfig.getSourceDb().getDbType() +")：");
		log.info("+     driverClassName :"+ syncConfig.getSourceDb().getDriverClassName());
		log.info("+     url :"+ syncConfig.getSourceDb().getUrl());
		log.info("+     username :"+ syncConfig.getSourceDb().getUserName());
		log.info("+     password :"+ syncConfig.getSourceDb().getPassword());
		log.info("+-------");
		log.info("+ 目标数据(" + syncConfig.getTagDb().getDbType() +")：");
		log.info("+     driverClassName :"+ syncConfig.getTagDb().getDriverClassName());
		log.info("+     url :"+ syncConfig.getTagDb().getUrl());
		log.info("+     username :"+ syncConfig.getTagDb().getUserName());
		log.info("+     password :"+ syncConfig.getTagDb().getPassword());
		log.info("+-------");
		log.info("+ 其他配置：");
		log.info("+     pageSize :"+ syncConfig.getOther().getPageSize());
		log.info("---------------------------------------------------");
		for (SyncBean syncBean : syncConfig.getSyncBeans()) {
			log.info("(" + syncBean.getSyncIndex() + "). [" + syncBean.getSyncType() + "]");
			if ("custom".equals(syncBean.getSyncType())) {
				log.info("自定义处理类：" + syncBean.getClassName());
			} else {
				TableInfo tableInfo = syncBean.getTableInfo();
				log.info("》表 [" + tableInfo.getSourceTableName() + "] ====> [" + tableInfo.getTagTableName() + "]" + (syncBean.isPageSync()?"(分页同步)":"(不分页同步)") +"  字段映射如下：");
				
				for (FieldInfo fieldInfo : tableInfo.getFieldInfos()) {
					String itemFieldHandleLog = "";
					if (fieldInfo.getSourceFieldName() != null && fieldInfo.getSourceFieldName().equals(tableInfo.getSourceTableIdName())) {//这个是主键字段
						itemFieldHandleLog += "key";
					}
					if (!"".equals(itemFieldHandleLog) && fieldInfo.getHandleDiffType() != 0) {
						itemFieldHandleLog += ", ";
					}
					if (Integer.valueOf(1).equals(fieldInfo.getHandleDiffType())) {
						itemFieldHandleLog += "以目标字段为准处理源数据";
					}
					if (!"".equals(itemFieldHandleLog)) {
						itemFieldHandleLog = "<" + itemFieldHandleLog + ">";
					}
					log.info("    " + itemFieldHandleLog + fieldInfo.getSourceFieldName() + "[" + fieldInfo.getSourceFieldType() + "] => " + fieldInfo.getTagFieldName() + "[" + fieldInfo.getTagFieldType() + "]");
				}
			}
		}
		
		log.info("+-+-+-+-+-+-+-+-+-+-+-+-同步信息-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-end-+-+-+-+-+-");
		
	}
	
	public static SyncConfig getSyncConfig() {
		//如果开启了断点续传，则读一下同步状态文件，把已同步的节点去掉
		if (syncConfig.getOther().isBreakpointContinue()) {
			try {
				Map<Integer, Integer> map = SyncStatusManager.getSyncStatus().getStatusMap();
				List<SyncBean> list = syncConfig.getSyncBeans();
				Iterator<SyncBean> iterator = list.iterator();
				while(iterator.hasNext()) {
					SyncBean item = iterator.next();
					if (null != map.get(item.getSyncIndex()) && map.get(item.getSyncIndex()) == Constant.SYNC_STATUS_FINISH) {
						iterator.remove();
					}
				}
				
			} catch (IOException e) {
				throw new SyncConfigException("获取同步状态文件异常");
			}//必须把异常往往上抛，否则可能会导致同步数据冲突
			
		}
		return syncConfig;
	}
	
	/**
	 * 设置参数
	 * @param props
	 * @param sourceDb
	 * @param tagDb
	 */
	private void setProperties(Properties props, DbInfo sourceDb, DbInfo tagDb) {
		String proper_user = ".user";
		String proper_password = ".password";
		String proper_driverClass = ".driverClass";
		String proper_jdbcUrl = ".jdbcUrl";
		String proper_ds_source = "source.";
		String proper_ds_tag = "tag."; 
		
		//添加源数据源配置
		String proper_source_p = proper_ds_source + sourceDb.getDbType();
		
		props.setProperty(proper_source_p + proper_user, sourceDb.getUserName());
		props.setProperty(proper_source_p + proper_password, sourceDb.getPassword());
		props.setProperty(proper_source_p + proper_driverClass, sourceDb.getDriverClassName());
		props.setProperty(proper_source_p + proper_jdbcUrl, sourceDb.getUrl());
		
		//添加目标数据源配置
		String proper_tag_p = proper_ds_tag + tagDb.getDbType();
		
		props.setProperty(proper_tag_p + proper_user, tagDb.getUserName());
		props.setProperty(proper_tag_p + proper_password, tagDb.getPassword());
		props.setProperty(proper_tag_p + proper_driverClass, tagDb.getDriverClassName());
		props.setProperty(proper_tag_p + proper_jdbcUrl, tagDb.getUrl());
		
		//把没用的数据源配置上空字符串“”
		String[] allDbType = new String[] {Constant.DBTYPE_SQLSERVER, Constant.DBTYPE_ORACLE, Constant.DBTYPE_MYSQL};
		List<String> sourceNoUsedDbType = new ArrayList<String>(allDbType.length-1);
		List<String> tagNoUsedDbType = new ArrayList<String>(allDbType.length-1);
		
		for (String dbType : allDbType) {
			if (!dbType.equals(sourceDb.getDbType())) {
				sourceNoUsedDbType.add(dbType);
			}
			if (!dbType.equals(tagDb.getDbType())) {
				tagNoUsedDbType.add(dbType);
			}
		}
		
		for (String dbType : sourceNoUsedDbType) {
			props.setProperty(proper_ds_source + dbType + proper_user, "");
			props.setProperty(proper_ds_source + dbType + proper_password, "");
			props.setProperty(proper_ds_source + dbType + proper_driverClass, "");
			props.setProperty(proper_ds_source + dbType + proper_jdbcUrl, "");
		}
		
		for (String dbType : tagNoUsedDbType) {
			props.setProperty(proper_ds_tag + dbType + proper_user, "");
			props.setProperty(proper_ds_tag + dbType + proper_password, "");
			props.setProperty(proper_ds_tag + dbType + proper_driverClass, "");
			props.setProperty(proper_ds_tag + dbType + proper_jdbcUrl, "");
		}
		
	}
	
	//根据节点获取同步信息(defaultIndex: 当任务序号没填时，按顺序从1作为默认序号)
	private SyncBean getSyncBean(Element element, Integer defaultIndex, boolean defaultPageSync) {
		SyncBean syncBean = new SyncBean();
		syncBean.setSyncType(element.attributeValue("sync-type"));
		syncBean.setSyncIndex(Strings.isNullOrEmpty(element.attributeValue("sync-index"))?defaultIndex:Integer.parseInt(element.attributeValue("sync-index")));
		syncBean.setParent(Strings.isNullOrEmpty(element.attributeValue("parent"))?0:Integer.parseInt(element.attributeValue("parent")));
		if ("simple".equals(element.attributeValue("sync-type"))) {
			syncBean.setTableInfo(getTableInfo(element.element("table")));
			String elePageSync = element.attributeValue("page-sync");
			if ("true".equals(elePageSync) || "false".equals(elePageSync)) {
				syncBean.setPageSync("true".equals(element.attributeValue("page-sync"))?true:false);
			} else {
				syncBean.setPageSync(defaultPageSync);
			}
			
		} else if ("custom".equals(element.attributeValue("sync-type"))) {
			syncBean.setClassName(element.elementText("class-name"));
		} else {
			throw new SyncConfigException("sync-type配置异常(目前只有simple和custom)");
		}
		
		return syncBean;
	}
	
	//根据节点获取表信息
	private TableInfo getTableInfo(Element element) {
		TableInfo tableInfo = new TableInfo();
		List<FieldInfo> fieldInfos = new ArrayList<>();
		
		tableInfo.setSourceTableName(element.attributeValue("source-table-name"));
		//目标表名不写就默认和源表名一样
		tableInfo.setTagTableName(Strings.isNullOrEmpty(element.attributeValue("tag-table-name"))?tableInfo.getSourceTableName():element.attributeValue("tag-table-name"));
		
		Element itemElement = null;
		FieldInfo itemFieldInfo = null;
		for (Iterator i = element.elementIterator(); i.hasNext(); ) {
			itemFieldInfo = new FieldInfo();
			
			itemElement = (Element) i.next();
			itemFieldInfo.setSourceFieldName(itemElement.attributeValue("source-field-name"));
			itemFieldInfo.setSourceFieldType(itemElement.attributeValue("source-field-type"));
			
			
			itemFieldInfo.setTagFieldName(Strings.isNullOrEmpty(itemElement.attributeValue("tag-field-name"))?itemFieldInfo.getSourceFieldName():itemElement.attributeValue("tag-field-name"));
			itemFieldInfo.setTagFieldType(Strings.isNullOrEmpty(itemElement.attributeValue("tag-field-type"))?itemFieldInfo.getSourceFieldType():itemElement.attributeValue("tag-field-type"));
			itemFieldInfo.setTagFieldNullable("false".equals(itemElement.attributeValue("tag-field-nullable"))?false:true);
			
			if("true".equals(itemElement.attributeValue("source-id-flag"))) {
				itemFieldInfo.setSourceFieldKey(true);
				tableInfo.setSourceTableIdName(itemFieldInfo.getSourceFieldName());
			} else {
				itemFieldInfo.setSourceFieldKey(false);
			}
			
			List sourceWhereItems = itemElement.elements("source-where-sql");
			if (itemElement != null && sourceWhereItems.size() > 0) {
				String sourceWhereSql = ((Element) sourceWhereItems.get(0)).getText();
				if (!StringUtils.isEmpty(sourceWhereSql)) {
					itemFieldInfo.setSourceWhere(new Where(sourceWhereSql));
				}
			}
			if (Strings.isNullOrEmpty(itemElement.attributeValue("handle-diff-type"))) {
				itemFieldInfo.setHandleDiffType(0);//默认不处理
			} else {
				int reHandleDiffType = Integer.parseInt(itemElement.attributeValue("handle-diff-type"));
				if (reHandleDiffType < 0 || reHandleDiffType > 1) {
					//配置了，但配置出错，说明执行人有要处理的想法，所以需要及时通过报错的方式提醒他确认自己的想法
					throw new SyncConfigException("[字段格式不匹配处理方式]handle-diff-type配置错误");
				} else {
					itemFieldInfo.setHandleDiffType(reHandleDiffType);
				}
			}
			
			fieldInfos.add(itemFieldInfo);
		}
		tableInfo.setFieldInfos(fieldInfos);
		return tableInfo;
	}
	
	//根据节点获取其他配置
	private Other getOther(Element element) {
		Other other = new Other();
		
		boolean breakpointContinue = false;//默认关闭断点续传(一般只给开发人员使用)
		String breakpointContinueStr = element.elementTextTrim("breakpoint-continue");
		if (null != breakpointContinueStr && "true".equals(breakpointContinueStr.toLowerCase())) {
			breakpointContinue = true;
		}
		
		boolean pageSync = true;//默认开启分页同步
		String pageSyncStr = element.elementTextTrim("page-sync");
		if (null != pageSyncStr && "false".equals(pageSyncStr.toLowerCase())) {
			pageSync = false;
		}
		
		Integer pageSize = 100;//默认每页100条(经测试100左右时，同步速度比较快)
		try {
			pageSize = Integer.parseInt(element.elementTextTrim("page-size"));
		} catch (Exception e) {
			log.info("pageSize设置有误，默认为100");
		}
		
		other.setBreakpointContinue(breakpointContinue);
		other.setPageSync(pageSync);
		other.setPageSize(pageSize);
		return other;
	}
	
	//根据节点获取数据库信息
	private DbInfo getDbInfo(Element element) {
		DbInfo dbinfo = new DbInfo();
		dbinfo.setDbType(element.attributeValue("dbtype"));
		dbinfo.setDriverClassName(element.elementTextTrim("driverClassName"));
		dbinfo.setPassword(element.elementTextTrim("password"));
		dbinfo.setUrl(element.elementTextTrim("url"));
		dbinfo.setUserName(element.elementTextTrim("username"));
		return dbinfo;
	}
	
	//获取root节点
	private Element getRoot(InputStream in) throws MalformedURLException, DocumentException {
		SAXReader reader = new SAXReader();
		Document document = reader.read(in);
		return document.getRootElement();
	}
	
	/**
	 * 存档当前同步状态（到sync-status.properties）
	 * @throws IOException 
	 */
	public static void archiveSyncStatus(Integer syncStatus, Integer syncTime) throws IOException {
		Date now = new Date();
		String comment = "LastSyncStatus:上次同步状态（和节点同步状态一致。2：完成(默认)  3.失败）；"
				+ "LastSyncDate：上次同步日期；LastSyncTime：上次同步耗时；TotalSyncTime：总共用时（如果上次失败，"
				+ "并且启用断点续传，则时间累加）；TotalTryNumber：总共尝试次数";
		
		SyncStatus syncStatusEntity = SyncStatusManager.getSyncStatus();
		
		//重新计算两个total的条件：配置启用断点续传 + 上次同步失败
		Integer TotalSyncTime = null;
		Integer TotalTryNumber = null;
		if (!syncConfig.getOther().isBreakpointContinue() || !Integer.valueOf(2).equals(syncStatusEntity.getLastSyncStatus())) {
			Integer lastTotalSyncTime = syncStatusEntity.getTotalSyncTime();
			if (null != lastTotalSyncTime && lastTotalSyncTime > 0) {
				TotalSyncTime = lastTotalSyncTime + syncTime;
			}
			
			Integer lastTotalTryNumber = syncStatusEntity.getTotalTryNumber();
			if (null != lastTotalTryNumber && lastTotalTryNumber > 0) {
				TotalTryNumber = lastTotalTryNumber + 1;
			}
		}
		
		syncStatusEntity.setComment(comment);
		syncStatusEntity.setLastSyncStatus(syncStatus);
		syncStatusEntity.setLastSyncDate(now);
		syncStatusEntity.setLastSyncTime(syncTime);
		syncStatusEntity.setTotalSyncTime(TotalSyncTime);
		syncStatusEntity.setTotalTryNumber(TotalTryNumber);
		
		SyncStatusManager.setSyncStatus(syncStatusEntity);
	}
	
	
}
